/**
 * Copyright © 2013 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.creativor.rayson.serialize;

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

/**
 * @author creativor
 * 
 */
public final class Types {

	private static final Map<Type, SerializableType<?>> CLASS_TYPE_MAP = new HashMap<>();
	private static final Map<Byte, SerializableType<?>> CODE_TYPE_MAP = new HashMap<>();

	private static final SerializableType<?> NULL = new NullType((byte) -1);
	private static final SerializableType<?> ENUM = new EnumType((byte) 20);
	private static final SerializableType<?> ARRAY = new ArrayType((byte) 21);
	private static final SerializableType<?> OBJECT = new ObjectType((byte) 22);
	private static final SerializableType<?> REFERENCE = new ReferenceType((byte) 30);

	private static final SerializableType<?> BOOLEAN = new BooleanType((byte) 1);
	private static final SerializableType<?> BYTE = new ByteType((byte) 2);
	private static final SerializableType<?> CHAR = new CharType((byte) 3);
	private static final SerializableType<?> SHORT = new ShortType((byte) 4);
	private static final SerializableType<?> INT = new IntType((byte) 5);
	private static final SerializableType<?> DOUBLE = new DoubleType((byte) 6);
	private static final SerializableType<?> FLOAT = new FloatType((byte) 7);
	private static final SerializableType<?> LONG = new LongType((byte) 8);
	private static final SerializableType<?> STRING = new StringType((byte) 9);

	static {

		// Setup <Object class-> serialize type> map.
		CLASS_TYPE_MAP.put(Boolean.class, BOOLEAN);
		CLASS_TYPE_MAP.put(Boolean.TYPE, BOOLEAN);
		CLASS_TYPE_MAP.put(Byte.class, BYTE);
		CLASS_TYPE_MAP.put(Byte.TYPE, BYTE);
		CLASS_TYPE_MAP.put(Character.TYPE, CHAR);
		CLASS_TYPE_MAP.put(Character.class, CHAR);
		CLASS_TYPE_MAP.put(Short.TYPE, SHORT);
		CLASS_TYPE_MAP.put(Short.class, SHORT);
		CLASS_TYPE_MAP.put(Integer.TYPE, INT);
		CLASS_TYPE_MAP.put(Integer.class, INT);
		CLASS_TYPE_MAP.put(Double.TYPE, DOUBLE);
		CLASS_TYPE_MAP.put(Double.class, DOUBLE);
		CLASS_TYPE_MAP.put(Float.TYPE, FLOAT);
		CLASS_TYPE_MAP.put(Float.class, FLOAT);
		CLASS_TYPE_MAP.put(Long.TYPE, LONG);
		CLASS_TYPE_MAP.put(Long.class, LONG);
		CLASS_TYPE_MAP.put(String.class, STRING);

		// Setup <type code-> serialize type> map.
		putToCodeMap(NULL);
		putToCodeMap(ENUM);
		putToCodeMap(ARRAY);
		putToCodeMap(BOOLEAN);
		putToCodeMap(BYTE);
		putToCodeMap(CHAR);
		putToCodeMap(SHORT);
		putToCodeMap(INT);
		putToCodeMap(DOUBLE);
		putToCodeMap(FLOAT);
		putToCodeMap(LONG);
		putToCodeMap(STRING);
		putToCodeMap(OBJECT);

	}

	public static SerializableType<?> getType(final byte type) throws UnsupportedTypeException {
		final SerializableType<?> st = CODE_TYPE_MAP.get(type);
		if (st != null)
			return st;
		throw new UnsupportedTypeException("type " + type + "  is unsupported");

	}

	private static void putToCodeMap(final SerializableType<?> type) {
		CODE_TYPE_MAP.put(type.getType(), type);
	}

	/**
	 * @param objType
	 * @return
	 * @throws UnsupportedTypeException
	 */
	public static SerializableType<?> typeOf(final Class<?> objType)
			throws UnsupportedTypeException {
		if (objType.isEnum())
			return ENUM;
		if (objType.isArray())
			return ARRAY;

		final SerializableType<?> type = CLASS_TYPE_MAP.get(objType);

		if (type != null)
			return type;

		return OBJECT;

		// throw new UnsupportedTypeException(objType + " is an unsupported serializable type");
	}

	/**
	 * @param obj
	 * @return
	 */
	public static SerializableType<?> typeOf(final Object obj) throws UnsupportedTypeException {
		if (obj == null)
			return NULL;
		final Class<?> objType = obj.getClass();
		return typeOf(objType);
	}

	private Types() {
		// Forbidden.
	}
}